package sketch.generator;

import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import sketch.BatchExecution;
import sketch.ExecutableSequenceContent;
import sketch.MethodExecOutcome;
import sketch.SMethod;
import sketch.SketchSequence;
import sketch.util.Checker;
import sketch.util.Globals;
import sketch.util.SketchUtil;

/***
 * T could be the type of sequence (randoop-style)
 * */
public abstract class SequenceGenerator<T extends SketchSequence> extends Generator<Object> {
	/**
	 * A description of the  generated sequence
	 * **/
	public String description = null;
	/**
	 * The receiver object
	 * */
	private /*final*/ Object reciever;
	
	/**
	 * Keep the object id
	 * */
	private /*final*/ int receiverId;
	
	/**
	 * The invoke method(s)
	 * */
	public final SMethod smethod;
	/**
	 * where the value comes from
	 * */
	public ValueGenerator<? extends Object> valueGenerator;
	
	/**
	 * The sequence to be executed
	 * */
	protected ExecutableSequenceContent[] contents = null;
	
	protected BatchExecution batchExecution = null;
	
	protected final LinkedHashMap<String, ExhaustiveTypedValueGenerator<Object>> typedValueGenerators;
	
	public SequenceGenerator(Object receiver, SMethod smethod, ValueGenerator<? extends Object> generator) {
		Checker.checkNull(smethod, "the sketching method object could not be null!");
		if(!smethod.isConstructor()) {
		  Checker.checkNull(receiver, "the receiver object could not be null!");
		}
		Checker.checkNull(generator, "the value generator could not be null!");
		this.reciever = receiver;
		this.receiverId = System.identityHashCode(this.reciever);
		this.smethod = smethod;
		this.valueGenerator = generator;
		//initialize the typedValueGenerator
		Object[] allObjs = generator.all();
		//create a value generator for each method
		this.typedValueGenerators = new LinkedHashMap<String, ExhaustiveTypedValueGenerator<Object>>();
		for(String methodName : smethod.methodNames) {
			Method method = this.smethod.findMethod(methodName);
			Class<?>[] argTypes = method.getParameterTypes();
			ExhaustiveTypedValueGenerator<Object> typeValueGenerator
			    = new ExhaustiveTypedValueGenerator<Object>(argTypes, allObjs);
			this.typedValueGenerators.put(methodName, typeValueGenerator);
		}
	}
	
	public Object getReceiver() {
		return this.reciever;
	}
	
	public int getReceiverId() {
		return this.receiverId;
	}
	
	public void resetReceiver(Object receiver) {
		this.reciever = receiver;
		this.receiverId = System.identityHashCode(receiver);
	}
	
	public abstract ExecutableSequenceContent[] executables();
	
	/**
	 * Using the current receiver to execute the current method reflectively
	 * */
	public void execute() {
		this.contents = executables();
		this.batchExecution = SketchUtil.batchExecution(this.contents);
		//return batchExecution;
	}
	
	public ExecutableSequenceContent[] getExecutionContents() {
		Checker.checkNull(this.contents, "The execution contents should not be null.");
		return this.contents;
	}
	
	public BatchExecution getExecutionResults() {
		Checker.checkNull(this.batchExecution, "The batch execution result should not be null.");
		return this.batchExecution;
	}
	
	/**
	 * The following three methods have not been used yet.
	 * */
//	public MethodExecOutcome[] execute() {
//		//if there is more than one method in SMethod, we will return null immediate, 
//		//since you never what the method execution outcome looks like
//		if(this.smethod.getMethodNum() != 1) {
////			throw new RuntimeException("It is hard to tell the the execution outcome for method: "
////					+ this.smethod.methodsToString());
//			this.outcomes = null;
//			this.outcomeIds = null;
//			return this.outcomes;
//		}
//		//if there is only one method, regardless of repeatedly executing or not
//		MethodExecOutcome[] executionResults = executionResult();
//		if(executionResults == null) {
//			//there is no outcome, such as for void method
//			this.outcomes = new MethodExecOutcome[0];
//			this.outcomeIds = new int[0];
//		} else {
//			//compute the outcome of method execution
//			int length = executionResults.length;
//			this.outcomes = new MethodExecOutcome[length];
//			this.outcomeIds = new int[length];
//			for(int i = 0 ; i < length; i++) {
//				Checker.checkNull(executionResults[i], "The execution result could not be null!");
//				this.outcomes[i] = executionResults[i];
//				this.outcomeIds[i] = System.identityHashCode(executionResults[i]);
//			}
//		}
//		return this.outcomes;
//	}
//	
//	public MethodExecOutcome findMethodExecOutcome(int objectId) {
//		if(this.outcomes == null) {
//			throw new RuntimeException("The method exec outcome fields have not been initialized. please call execute() first! "
//					+ Globals.lineSep + "Or there could be multiple methods and can not have clear idea or which method you want!"
//					+ Globals.lineSep + "method list: " + this.smethod.methodsToString());
//		}
//		int index = -1;
//		for(int i = 0; i < this.outcomeIds.length; i++) {
//			if(objectId == this.outcomeIds[i]) {
//				index = i;
//				break;
//			}
//		}
//		//no method exec out come found
//		if(index == -1) {
//			return null;
//		}
//		return this.outcomes[index];
//	}
	
//	protected abstract MethodExecOutcome[] executionResult();
	
	@Override
	public void reset() {
		super.reset();
		this.valueGenerator.reset();
		
		for(ExhaustiveTypedValueGenerator<Object> gen : this.typedValueGenerators.values()) {
			if(gen.generators.isEmpty()) {
				continue;
			}
			gen.reset();
		}
	}
	
	/**
	 * To see if the methods are valid for the given receiver object
	 * XXX this method is not used yet
	 * */
	protected void checkMethodValidity(Object receiver, SMethod smethod) {
	    Class<?> receiverClass = receiver.getClass();
		List<String> methodNames = smethod.methodNames;
		Set<String> allMethods = new HashSet<String>();
		for(Method m : receiverClass.getMethods()) {
			allMethods.add(m.toGenericString());
		}
		String currentCheck = null;
		for(String methodName : methodNames) {
			currentCheck = methodName;
			if(allMethods.contains(methodName)) {
				return;
			}
		}
		Checker.checkTrue(false, "Method: " + currentCheck + " does not exist in class: " + receiverClass);
	}

	/**
	 * Figure out the way to compose a sequence
	 * */
	protected abstract SketchSequence composeSequence();
	
	/**
	 * Print out the code
	 * */
	public abstract SketchSequence getGeneratedSequence();
}
